#include <iostream>
#include <string>
#include <algorithm>
#include <cstring>

#include <assert.h>
#include <cstdio>
#include <cstring>

#include "master.h"


using namespace std;

Master::Master(char* host, unsigned short port) : Server (host, port) {
	if (pthread_mutex_init(&slaveListLock, NULL)) {
		perror("");
	}
	cout << "Master initialized...." << endl;
}

Master::~Master() {
	if (pthread_mutex_destroy(&slaveListLock))
		perror("");
}

string Master::getMinIP(uint32_t reduce_size)
{
	//minHeap is shared, we need to lock it before reading/writing.
	list<Slave>::iterator it, minIt;
	int min = (*(slaves.begin())).aggregateLoad;

	if (pthread_mutex_lock(&slaveListLock)) {
		perror("Lock:");
	}

	if (slaves.empty()) {
		cout << "No Slaves!" << endl;

		if (pthread_mutex_unlock(&slaveListLock)) {
				perror("");
		}
		return NULL;
	}

	for (it = slaves.begin(); it != slaves.end(); it++) {
		if (min > (*it).aggregateLoad) {
			minIt = it;
			min = (*it).aggregateLoad;
		}
	}

	if (pthread_mutex_unlock(&slaveListLock)) {
		perror("Unlock:");
	}

	minIt->increaseLoad(reduce_size);

	return (*minIt).getIP();
}

void Master::handleClient(Socket *sock)
{
	//Request from a client. We do following:
	//1. Extract client's requirements from the message
	//2. Find the lowest loaded slave
	//3. Send back info
	//4. Update the load value of this slave
	uint32_t size;
	ssize_t dataSize, nBytes, length;
	bool b;
	string ip;

	ClientRequest request;

	ServerResponse response;

	cerr << "Handling a new client now..." << endl;
	while (true) {
		//Client first sends length of data and then the actual data
		nBytes = sock->Receive(&dataSize, sizeof(dataSize));

		cerr << "Client promised to send " << dataSize << "bytes" << endl;
		char request_cstr[dataSize + 1];

		//Receive a serialized protobuf string
		unsigned int bRec = 0;
		length = dataSize;

		while (bRec < dataSize) {
			nBytes = sock->Receive(request_cstr + bRec, length);
			length -= nBytes;
			bRec += nBytes;
		}

		cerr << "Complete data received from the client" << endl;
		request_cstr[dataSize + 1] = '\0';
		string temp(request_cstr);

		request.Clear();
		request.ParseFromString(temp);
		size = request.filesize();
		
		ip =  getMinIP(size);

		response.Clear();

		if (!ip.empty()) {
			cout << "Least loaded slave is: " << ip;
			response.add_ip(ip);
			//strncpy(reply.ip, ip.c_str(), 20);
		} else {
			cout <<" NULL IP" << endl;
			//strncpy(reply.ip, "_no_slave", 11);
			//Don't send anything inside the protobuf
		}

		string t;
		response.SerializeToString(&t);
		dataSize = t.size();

		//Send size of data to expect
		sock->Send(&dataSize, sizeof(dataSize));

		char response_cstr[dataSize + 1];
		strncpy(response_cstr, t.c_str(), t.size());
		response_cstr[dataSize] = '\0';

		nBytes = sock->Send(response_cstr, dataSize);
		cout << "Sent " << nBytes << "/" << dataSize << " to client\n";
	}

}

void Master::handleSlave(Socket *sock)
{
	//Update from a slave. We do following:
	//1. Update the load value of this slave
	Load load;

	ssize_t nBytes, dataSize, length, bRec;
	string temp;

	cerr << "Handling a new slave now" << endl;
	while (true) {
		cerr << "Load update from an existing slave" << endl;
		nBytes = sock->Receive(&dataSize, sizeof(dataSize));

		cerr << "Client promised to send " << dataSize << "bytes" << endl;
		char load_cstr[dataSize + 1];

		while (bRec < dataSize) {
					nBytes = sock->Receive(load_cstr + bRec, length);
					length -= nBytes;
					bRec += nBytes;
		}
		load_cstr[dataSize] = '\0';

		temp.assign(load_cstr);

		load.Clear();
		load.ParseFromString(temp);

		cout << "Slave's IP:" << load.ip() << endl;
		changeLoad(load);
	}
}

Slave s;
//O(n) currently
Slave & Master::getMinLoaded(bool &b)
{
	//minHeap is shared, we need to lock it before reading/writing.
	list<Slave>::iterator it, minIt;
	int min = (*(slaves.begin())).aggregateLoad;

	if (pthread_mutex_lock(&slaveListLock)) {
		perror("Lock:");
	}

	if (slaves.empty()) {
		cout << "No Slaves!" << endl;

		if (pthread_mutex_unlock(&slaveListLock)) {
				perror("");
		}
		return ::s;
	}

	for (it = slaves.begin(); it != slaves.end(); it++) {
		if (min > (*it).aggregateLoad) {
			minIt = it;
			min = (*it).aggregateLoad;
		}
	}

	if (pthread_mutex_unlock(&slaveListLock)) {
		perror("Unlock:");
	}

	return *minIt;
}

//O(n) currently. Use a ip->slave map
void Master::changeLoad(Load &s)
{
	slaveList::iterator it;
	string ip(s.ip());

	if (pthread_mutex_lock(&slaveListLock)) {
		perror("");
	}
	for (it = slaves.begin(); it != slaves.end(); it++) {
		if (ip.compare((*it).ip) == 0)
			break;
	}

	//assert(it != slaves.end());
	if (it == slaves.end()) {	//new slave joined in
		//create new Slave object
		Slave *newSlave = new Slave(s, calculateLoad(s));
		//anticipate a new client to be less loaded
		slaves.push_front(*newSlave);
		pthread_mutex_unlock(&slaveListLock);
		cout << "New slave joins in: " << string(s.ip());
		return;
	}

//	s.ip[19] = '\0';
	(*it).updateLoad(s);

	if (pthread_mutex_unlock(&slaveListLock)) {
		perror("");
	}
}


void Master::start(void)
{
	AcceptConnections();
}

void Master::HandleRequest(Socket *sock) {
	//The request can be either from a client or from a slave.
	//We distinguish these two by first reading a uint32_t requestID.
	//1: Client, 2:Slave

	uint32_t reqID;

	sock->Receive((void *) &reqID, sizeof(reqID));

	switch (reqID) {

	case	client_request :	handleClient(sock);
	break;
	case 	slave_load	   :	handleSlave(sock);
	break;
	default	 			   :	cerr << "Bad Request ID, dropping connection..." << endl;

	}

}

//fileSize: size of file to be compiled
void Slave::increaseLoad(uint32_t fileSize) {
	//TODO:improve this
	idle_perc *= 0.8;
}

void Slave::updateLoad(const Load & s)
{
	idle_perc  = s.idle_perc();
	rps = s.rps();
	wps = s.wps();

	//TODO: Better formula
	aggregateLoad = (int) idle_perc;
}


